<?php
/**
 * Soar Framework - Lightweight and simple to use PHP Framework
 *
 * Soar is free software and is distributed WITHOUT ANY WARRANTY
 *
 * @package Soar
 * @version 1.0 $Id: SoarAutoloader.php 2 2012-04-06 21:50:33Z shay@shayanderson.com $
 * @copyright Copyright (c) 2012 ShayAnderson.com
 * @license http://www.gnu.org/licenses/gpl.html GPL License
 * @link http://www.shayanderson.com/projects/soar-php-framework.htm
 */

/**
 * Soar Framework Autoloader Class - Autoload framework classes
 *
 * @package Soar
 * @name SoarAutoloader
 * @author Shay Anderson 03.12
 *
 * @example
 * // set class file extension (optional, default: '.php')
 * // SoarAutoloader::$class_ext = '.php';
 * SoarAutload::addClassPath('example/class/path');
 * // -- OR multiple class paths --
 * SoarAutoloader::addClassPath(array(
 *		'example/class/path1',
 *		'example/class/path2'
 * ));
 * // -- OR autoload exact class files --
 * SoarAutoloader::addClassFile(array(
 *		'path/to/class' => 'ExampleClass',
 *		-- OR one path for multiple class files --
 *		'another/path/to/classes' => array(
 *			'OneClass',
 *			'TwoClass
 *		)
 * ))
 */
final class SoarAutoloader
{
	/**
	 * Class files for autoloading
	 *
	 * @var array ('ExampleClass' => 'path/to/class', [, ...])
	 */
	private static $__class_files = array();

	/**
	 * Loaded class files cache
	 *
	 * @var array
	 */
	private static $__class_files_loaded = array();

	/**
	 * Class path for autoloading method
	 *
	 * @var string
	 */
	private $__class_path;

	/**
	 * Document root path
	 *
	 * @var string
	 */
	private static $__doc_root;

	/**
	 * Auload registered flag
	 *
	 * @var bool
	 */
	private static $__is_autoload_registered = false;

	/**
	 * Debugger class loaded flag
	 *
	 * @var bool
	 */
	private static $__is_debugger_ready = false;

	/**
	 * Initialized flag
	 *
	 * @var bool
	 */
	private static $__is_init = false;

	/**
	 * Registered class paths
	 *
	 * @var array
	 */
	private static $__paths = array();

	/**
	 * Class file extension
	 *
	 * @var string
	 */
	public static $class_ext = '.php';

	/**
	 * Initialize
	 *
	 * @param string $class_path
	 */
	public function __construct($class_path)
	{
		$this->__class_path = $class_path;
	}

	/**
	 * Format path by prepend doc root and/or appending tail slash
	 *
	 * @param string $path
	 * @param bool $prepend_doc_root
	 * @return string
	 */
	private static function __formatPath($path = NULL, $prepend_doc_root = true)
	{
		if(!empty($path))
		{
			$path = trim($path);
			if($prepend_doc_root && !empty(self::$__doc_root) && strpos($path, self::$__doc_root) === false)
			{
				// prepend path with doc root
				$path = self::$__doc_root . ltrim($path, DIRECTORY_SEPARATOR);
			}
			$path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
		}
		return $path;
	}

	/**
	 * Initialize auloader
	 *
	 * @return void
	 */
	private static function __init()
	{
		if(!self::$__is_init)
		{
			// flush existing autoloads
			spl_autoload_register(NULL, false);
			self::$__doc_root = SOAR_DIR_BASE;
			self::$__is_init = true;
		}
	}

	private static function __logLoaded($class_name, $class_path)
	{
		if(!self::$__is_debugger_ready && $class_name == 'SoarDebugger')
		{
			self::$__is_debugger_ready = true;
		}
		else if(self::$__is_debugger_ready)
		{
			if(count(self::$__class_files_loaded) > 0)
			{
				foreach(self::$__class_files_loaded as $v)
				{
					SoarDebugger::log(__CLASS__ . ": class \"{$v}\" loaded", SoarDebugger::LEVEL_DEBUG);
				}
				self::$__class_files_loaded = array();
			}
			SoarDebugger::log(__CLASS__ . ": class \"{$class_path}\" loaded", SoarDebugger::LEVEL_DEBUG);
		}
		else
		{
			self::$__class_files_loaded[] = $class_path;
		}
	}

	/**
	 * Register autload
	 *
	 * @param string $path
	 * @return bool
	 */
	private static function __registerAutload($path)
	{
		self::__init();
		if(spl_autoload_register(array(new self($path), 'autoload')))
		{
			self::$__is_autoload_registered = true;
			return true;
		}
		return false;
	}

	/**
	 * Register class files for autoloading
	 *
	 * @param array $file
	 *		('path/to/class' => 'ExampleClass', [, ...])
	 *		-- OR --
	 *		('path/to/classes' => array('ExampleClass', 'AnotherClass', [, ...]) [, ...])
	 * @param bool $prepend_doc_root
	 * @return void
	 */
	public static function addClassFile(array $file, $prepend_doc_root = true)
	{
		self::__init();
		foreach($file as $path => $class)
		{
			$path = self::__formatPath($path, $prepend_doc_root);
			if(is_array($class))
			{
				foreach($class as $v)
				{
					self::$__class_files[$v] = $path;
				}
			}
			else
			{
				self::$__class_files[$class] = $path;
			}
		}
		if(!self::$__is_autoload_registered)
		{
			// no registered autoload, register
			self::__registerAutload(NULL);
		}
	}

	/**
	 * Register class path for autoloading
	 *
	 * @param mixed $path (string|array)
	 * @param bool $prepend_doc_root
	 * @return void
	 */
	public static function addClassPath($path, $prepend_doc_root = true)
	{
		if(is_array($path))
		{
			foreach($path as $v)
			{
				self::addClassPath($v);
			}
		}
		else if(!empty($path))
		{
			self::__init();
			$path = self::__formatPath($path, $prepend_doc_root);
			if(!in_array($path, self::$__paths))
			{
				// register autload
				if(self::__registerAutload($path))
				{
					self::$__is_autoload_registered = true;
					self::$__paths[] = $path;
				}
			}
		}
	}

	/**
	 * Autload class
	 *
	 * @param string $class_name
	 * @return bool
	 */
	public function autoload($class_name)
	{
		if(isset(self::$__class_files[$class_name]))
		{
			require self::$__class_files[$class_name]
				. $class_name . self::$class_ext;
			self::__logLoaded($class_name, self::$__class_files[$class_name]
				. $class_name . self::$class_ext);
			return true;
		}
		if(empty($this->__class_path))
		{
			return false;
		}
		$class_file = $this->__class_path . $class_name . self::$class_ext;
		if(!file_exists($class_file))
		{
			return false;
		}
		self::__logLoaded($class_name, $class_file);
		require $class_file;
		return true;
	}

	/**
	 * Class file paths getter
	 *
	 * @return array
	 */
	public static function getClassFilePaths()
	{
		return self::$__class_files;
	}

	/**
	 * Class paths getter
	 *
	 * @return array
	 */
	public static function getClassPaths()
	{
		return self::$__paths;
	}
}
?>